package owg.util.editor2d.modes;

import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import owg.util.Calc;
import owg.util.editor2d.Editor2D;
import owg.util.editor2d.Editor2D.Axes;
import owg.util.editor2d.entity.Placeable2D;
import owg.util.editor2d.entity.Scaleable2D;
import owg.util.editor2d.operations.Transformation.TransformationData;
import owg.util.euclidian.V2D;
import owg.util.opengl.EGL;

public class Scale extends TransformationMode {
	public static float SLOW_MULTIPLIER = 0.1f;
	public enum ScaleMode
	{
		SCALE_AND_MOVE,
		MOVE_ONLY,
		SCALE_ONLY;
	}

	private ScaleMode mode;
	private Editor2D.Axes axis;
	float baseScale;
	V2D firstMouse, pivotPos;
	
	public Scale(Editor2D e, ScaleMode mode, Editor2D.Axes axis) {
		super(e, "Scale...");
		this.mode = mode;
		this.axis = axis;
		baseScale = 1;
		pivotPos = e.getPivotPos();
		firstMouse = e.eventHandler().getMousePosition();
	}

	@Override
	protected boolean numberInputAllowed()
	{
		return true;
	}

	@Override
	protected void numberInputChanged(KeyEvent e)
	{
		update(e==null?false:e.isShiftDown());
	}

	private void update(boolean slow)
	{
		V2D[] bases;
		if (axis == Editor2D.Axes.VIEW)
			bases = new V2D[]{V2D.X, V2D.Y};
		else if (axis == Editor2D.Axes.X)
			bases = new V2D[]{V2D.X};
		else if (axis == Editor2D.Axes.Y)
			bases = new V2D[]{V2D.Y};
		else if (axis == Editor2D.Axes.LOCAL_X)
			bases = new V2D[]{xOf(pivot)};
		else//LOCAL_Y
			bases = new V2D[]{yOf(pivot)};
		
		V2D firstOffset = firstMouse.clone().sub(pivotPos);
		V2D deviation = editor.eventHandler().getMousePosition().clone().sub(firstMouse);
		
		if(numberValue == null)
		{
			baseScale = 0;
			//Use the maximum scale along mouse dimensions.
			//The x-direction determines the sign.
			for(int i = bases.length-1; i>=0; i--)
			{
				final double o = firstOffset.dot(bases[i]);
				if(o != 0)
					baseScale = Calc.signedMax((slow?SLOW_MULTIPLIER:1)*(float) (deviation.dot(bases[i])/o), Math.abs(baseScale));
			}
			baseScale += 1;
		}
		else
			baseScale = (float)(double)numberValue;
		
		for (int i = 0; i < affectedIndices.length; i++)
		{
			TransformationData oldData = inverse.getData(i);
			int index = affectedIndices[i];
			Placeable2D p = editor.worldObjectsImmutable.get(index).a;
			
			/*final Placeable2D localPivot;
			
				localPivot = p;
			else
				localPivot = pivot;*/

			Float[] startScale = {oldData.xScale, oldData.yScale};
			if(startScale[0] == null)
				startScale[0] = 1.0f;
			if(startScale[1] == null)
				startScale[1] = 1.0f;
			if(mode != ScaleMode.MOVE_ONLY && p instanceof Scaleable2D)
			{
				//Individual local x/y are simplified, down below. 
				if(mode != ScaleMode.SCALE_ONLY || pivotMode == Editor2D.Pivot.FIRST_SELECTED || pivotMode == Editor2D.Pivot.FIRST_SELECTED
					|| (axis != Editor2D.Axes.LOCAL_X && axis != Editor2D.Axes.LOCAL_Y))
				{
					//Since the object's axes might not align with the scaling axes,
					//we need to find a best-fit solution which approximately scales
					//the object along the desired axis.
					V2D[] localAxes = {xOf(p), yOf(p)};
					float[] newScale = new float[2];

					if(bases.length == 2) //Two axes
					{
						for(int k = 0; k<2; k++)
							{
							double m = Calc.dist(localAxes[k].dot(bases[0]),localAxes[k].dot(bases[1]));
							newScale[k] = (float)(startScale[k] *(1-m+m*baseScale));
							}
					}
					else
					{
						for(int k = 0; k<2; k++)
							{
							double m = Math.abs(localAxes[k].dot(bases[0]));
							newScale[k] = (float)(startScale[k] *(1-m+m*baseScale));
							}
					}
					((Scaleable2D) p).setScale(snap(newScale[0]), snap(newScale[1]), true);
				}
				else if(axis == Editor2D.Axes.LOCAL_X)
					((Scaleable2D) p).setScale(snap(startScale[0]*baseScale), snap(startScale[1]), true);				
				else if(mode == ScaleMode.SCALE_ONLY && pivotMode != Editor2D.Pivot.FIRST_SELECTED && 
					pivotMode != Editor2D.Pivot.FIRST_SELECTED && axis == Editor2D.Axes.LOCAL_Y)
					((Scaleable2D) p).setScale(snap(startScale[0]), snap(startScale[1]*baseScale), true);
			}
			else
				((Scaleable2D) p).setScale(startScale[0], startScale[1], true);
			
			if(mode != ScaleMode.SCALE_ONLY)
			{
				V2D origOffset = new V2D(oldData.x, oldData.y).sub(pivotPos);
				V2D loc = new V2D();
				for(int k = 0; k<bases.length; k++)
					loc.add(bases[k], origOffset.dot(bases[k])*baseScale);
				loc.add(pivotPos);
				//Avoid zeroing out other axis if only scaling on one
				if(bases.length == 1)
				{
					V2D perp = bases[0].clone().perpendicular();
					loc.add(perp, perp.dot(origOffset));
				}
				editor.eventHandler().correctToGrid(loc, false);	
				p.setLocation(loc.x(), loc.y(), true);
			}
		}
		
		editor.editorUI().replaceMessage("Scale by "+numericalValueString(baseScale)+" along "+axis+" ("+mode.name()+")");
	}

	private float snap(float scale)
	{
		if(editor.settings.snapScaleEnabled.get())
			return (float) editor.eventHandler().correctToGrid(scale, editor.settings.snapScaleSize.get(), false);
		else
			return scale;
	}

	@Override
	protected void key2(KeyEvent e)
	{
		if(e.getKeyCode() == KeyEvent.VK_SHIFT && (e.getID() == KeyEvent.KEY_PRESSED || e.getID() == KeyEvent.KEY_RELEASED) && numberValue == null)
		{
			//Update when shift is toggled( but not in keyboard input mode, since slow does not apply there )
			update(e.isShiftDown());
		}
		else if(e.getID() == KeyEvent.KEY_PRESSED)
		{
			if(e.getKeyCode() == KeyEvent.VK_S)
			{
				ScaleMode[] modes = ScaleMode.values();
				mode = modes[Calc.cyclic(mode.ordinal()+(e.isShiftDown()?-1:1), modes.length)];
			}
			else if(e.getKeyCode() == KeyEvent.VK_X)
			{
				if(axis == Axes.X)
					axis = Axes.LOCAL_X;
				else if(axis == Axes.LOCAL_X)
					axis = Axes.VIEW;
				else
					axis = Axes.X;
			}
			else if(e.getKeyCode() == KeyEvent.VK_Y)
			{
				if(axis == Axes.Y)
					axis = Axes.LOCAL_Y;
				else if(axis == Axes.LOCAL_Y)
					axis = Axes.VIEW;
				else
					axis = Axes.Y;
			}
			else if(e.getKeyCode() == KeyEvent.VK_ENTER)
			{
				terminate(false);
				return;
			}
			else if(e.getKeyCode() == KeyEvent.VK_ESCAPE)
			{
				terminate(true);
				return;
			}
			else
				return;
			//Update only if axis or absolute changed
			editor.modeListeners.fireEvents(e.getModifiers());
			update(e.isShiftDown());
		}
	}
	public ScaleMode scaleMode()
	{
		return mode;
	}

	@Override
	protected String getMessage()
	{
		return "Scaled by "+numericalValueString(baseScale)+" along "+axis+" ("+mode+")";
	}

	@Override
	public void render(EGL<?> egl) {}

	@Override
	public void mouse(MouseEvent e)
	{
		if((e.getID() == MouseEvent.MOUSE_MOVED || e.getID() == MouseEvent.MOUSE_DRAGGED) && numberValue == null)
			update(e.isShiftDown());
		else if(e.getID() == MouseEvent.MOUSE_RELEASED)
		{
			if(e.getButton() == MouseEvent.BUTTON1)
				terminate(false);
			else if(e.getButton() == MouseEvent.BUTTON3)
				terminate(true);
		}
			
	}
}
